/**
 * Copyright 2019 吉鼎科技.
 *
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.engine.cmd.identity;

import cn.easyplatform.EasyPlatformWithLabelKeyException;
import cn.easyplatform.contexts.RecordContext;
import cn.easyplatform.dao.DaoException;
import cn.easyplatform.dao.EntityCallback;
import cn.easyplatform.dao.IdentityDao;
import cn.easyplatform.dos.FieldDo;
import cn.easyplatform.dos.OrgDo;
import cn.easyplatform.dos.Record;
import cn.easyplatform.dos.UserDo;
import cn.easyplatform.entities.BaseEntity;
import cn.easyplatform.entities.beans.LogicBean;
import cn.easyplatform.entities.beans.project.DeviceMapBean;
import cn.easyplatform.i18n.I18N;
import cn.easyplatform.interceptor.AbstractCommand;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.Strings;
import cn.easyplatform.lang.Times;
import cn.easyplatform.log.LogManager;
import cn.easyplatform.messages.request.LoginRequestMessage;
import cn.easyplatform.messages.response.LoginResponseMessage;
import cn.easyplatform.messages.response.SimpleResponseMessage;
import cn.easyplatform.messages.vos.AuthorizationVo;
import cn.easyplatform.messages.vos.LoginVo;
import cn.easyplatform.messages.vos.OrgVo;
import cn.easyplatform.messages.vos.RoleVo;
import cn.easyplatform.services.IProjectService;
import cn.easyplatform.services.ISubject;
import cn.easyplatform.type.EntityType;
import cn.easyplatform.type.FieldType;
import cn.easyplatform.type.IResponseMessage;
import cn.easyplatform.type.StateType;
import cn.easyplatform.util.EntityUtils;
import cn.easyplatform.util.IdentityUtils;
import cn.easyplatform.util.MessageUtils;
import cn.easyplatform.util.RuntimeUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

import static cn.easyplatform.dos.UserDo.ONLINE_NONE;
import static cn.easyplatform.dos.UserDo.STATE_LOCK;
import static cn.easyplatform.type.UserType.*;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
public class LoginCmd extends AbstractCommand<LoginRequestMessage> {

    private static Logger log = LoggerFactory.getLogger(LoginCmd.class);

    /**
     * @param req
     */
    public LoginCmd(LoginRequestMessage req) {
        super(req);
    }

    @Override
    public IResponseMessage<?> execute(final CommandContext cc) {
        LoginVo loginVo = req.getBody();
        Subject currentUser = SecurityUtils.getSubject();
        if (!currentUser.isAuthenticated() || cc.getUser() == null) {
            if (currentUser.isAuthenticated())
                ((ISubject) currentUser).setAuthenticate(false);
            UsernamePasswordToken token = new UsernamePasswordToken(
                    loginVo.getId(), loginVo.getPassword());
            token.setHost(loginVo.getIp());
            IdentityDao dao = cc.getIdentityDao(false);
            try {
                currentUser.login(token);
            } catch (AuthenticationException ex) {
                if (ex instanceof IncorrectCredentialsException) {// 密码不匹配
                    int tryTimes = dao.getLoginFailedTimes(loginVo.getId());
                    tryTimes++;
                    dao.updateLoginFailedTimes(loginVo.getId(), tryTimes,
                            loginVo.getIp());
                    if (tryTimes >= cc.getProjectService().getConfig().getMaxLoginFailedTimes()) {// 锁用户
                        dao.updateState(loginVo.getId(), loginVo.getIp(),
                                STATE_LOCK);
                        return MessageUtils.userTryLoginFailed(loginVo.getId());
                    }
                }
                log.warn("login", ex);
                if (ex.getCause() != null
                        && ex.getCause() instanceof DaoException) {
                    DaoException e = (DaoException) ex.getCause();
                    return MessageUtils.daoAccessError(e.getMessage(),
                            e.getArgs());
                }
                return MessageUtils.userNotFound(loginVo.getId());
            }
            UserDo userInfo = (UserDo) currentUser.getPrincipal();
            dao.setUser(userInfo);
            if (userInfo.getType() == TYPE_API) {//API用户不能登陆
                ((ISubject) currentUser).setAuthenticate(false);
                return MessageUtils.userNotFound(loginVo.getId());
            }
            if (userInfo.getType() != TYPE_ADMIN) {//仅允许管理账户和超级账户登陆，其它要检查
                try {
                    cc.getProjectService().validate(userInfo, true);
                } catch (EasyPlatformWithLabelKeyException e) {
                    ((ISubject) currentUser).setAuthenticate(false);
                    throw e;
                }
            }
            userInfo.setId(loginVo.getId());
            userInfo.setIp(loginVo.getIp());
            userInfo.setLocale(loginVo.getLocale());
            userInfo.setTimeout(cc.getProjectService().getConfig().getTimeToLive());
            userInfo.setOriginalPassword(loginVo.getPassword());
            // 检查用户有效期
            Date now = Times.toDay();
            if (userInfo.getValidStartDate() != null
                    && now.before(userInfo.getValidStartDate())) {// 设置的有效开始日期还未到
                ((ISubject) currentUser).setAuthenticate(false);
                return MessageUtils.userNotFound(loginVo.getId());
            }
            if (userInfo.getValidEndDate() != null
                    && now.after(userInfo.getValidEndDate())) {// 失效
                ((ISubject) currentUser).setAuthenticate(false);
                return MessageUtils.userNotFound(loginVo.getId());
            }
            int remainingDays = 0;
            if (userInfo.getValidDate() != null) {
                if (now.before(userInfo.getValidDate())) { // 密码日期还未到
                    ((ISubject) currentUser).setAuthenticate(false);
                    return MessageUtils.userPasswordExpired(loginVo.getId());
                }
                if (userInfo.getValidDays() > 0) {// 是否过期
                    int oneDay = 24 * 60 * 60 * 1000;
                    Date when = new Date(userInfo.getValidDate().getTime()
                            + userInfo.getValidDays() * oneDay);
                    if (now.after(when)) {
                        ((ISubject) currentUser).setAuthenticate(false);
                        return MessageUtils
                                .userPasswordExpired(loginVo.getId());
                    }
                    int days = cc.getProjectService().getConfig().getDaysRemaining();
                    if (days > 0) {
                        remainingDays = (int) ((now.getTime() - when.getTime()) / oneDay);
                        if (remainingDays > days)// 如果剩余天数大于指定的天数，重置为零
                            remainingDays = 0;
                    }
                }
            }
            int rs = cc.getProjectService().getSessionManager()
                    .checkUser(userInfo);
            if (rs > ONLINE_NONE) {// 表示用户已存在
                if (userInfo.getType() == TYPE_EXCLUSIVE) {
                    ((ISubject) currentUser).setAuthenticate(false);
                    return MessageUtils.userHasLogined(loginVo.getId());
                }
                if (userInfo.getType() == TYPE_ONLY || userInfo.getType() == TYPE_ADMIN) {
                    // 由客户端确认是否要中断已有的用户
                    cc.set("TempUser", userInfo);
                    return MessageUtils
                            .userLoginConfirm(loginVo.getId());
                }
            }
            userInfo.setLoginDate(new Date());
            userInfo.setLastAccessTime(new Date());
            EntityCallback<BaseEntity> cb = new EntityCallback<BaseEntity>() {
                @Override
                public BaseEntity getEntity(String entityId) {
                    return cc.getEntity(entityId);
                }

                public String getLabel(String code) {
                    return RuntimeUtils.getLabel(cc, code, loginVo.getLocale());
                }
            };
            if (!Strings.isBlank(cc.getProjectService().getConfig().getString("user.authentication.callback"))) {//执行登陆回调，例如针对特定的角色进行二次验证
                List<RoleVo> roles = dao.getUserRoles(userInfo.getId(), cc.getEnv()
                        .getDeviceType().getName(), cb);
                userInfo.setRoles(roles);
                cc.setUser(userInfo);
                IResponseMessage<?> resp = doCallback(cc, cc.getProjectService().getConfig().getString("user.authentication.callback"));
                if (!resp.isSuccess()) {
                    cc.logout();
                    return resp;
                }
                if (resp.getBody() instanceof String)
                    return resp;
            }
            if (userInfo.getType() == TYPE_ADMIN) {// 管理用户不需要去找机构
                userInfo.setOrg(new OrgDo());
                userInfo.getOrg().setId("admin");
                userInfo.getOrg().setName("admin");
                userInfo.getOrg().setExtraInfo(new HashMap<>());
                List<RoleVo> roles = dao.getUserRoles(userInfo.getId(), cc.getEnv()
                        .getDeviceType().getName(), cb);
                userInfo.setRoles(roles);
                LoginResponseMessage resp = new LoginResponseMessage(
                        new AuthorizationVo(null, roles));
                resp.setType(userInfo.getType());
                resp.setName(userInfo.getName());
                if (remainingDays > 0)
                    resp.setTooltip(I18N.getLabel("app.user.expiring", remainingDays));
                else if (cc.getProjectService().getConfig().getCheckPasswordPattern() != null) {
                    Matcher matcher = cc.getProjectService().getConfig().getCheckPasswordPattern().matcher(loginVo.getPassword());
                    if (!matcher.find())
                        resp.setTooltip(cc.getProjectService().getConfig().getCheckPasswordMessage());
                }
                userInfo.setState(StateType.START);
                cc.setUser(userInfo);
                cc.getSync().clear();
                IdentityUtils.log(cc, dao);
                LogManager.startUser(cc.getEngineConfiguration().getLogPath(), cc.getEnv().getId(), userInfo);
                return resp;
            }
            List<OrgVo> orgs = dao.getUserOrgs(loginVo.getId(), cb);
            if (orgs.isEmpty()) {
                cc.logout();
                return MessageUtils.userNotOrg(loginVo.getId());
            }
            try {
                LoginResponseMessage resp = null;
                if (orgs.size() == 1) {// 默认的机构号
                    String orgId = orgs
                            .remove(0).getId();
                    AuthorizationVo av = IdentityUtils.getUserAuthorization(cc, userInfo, orgId);
                    resp = new LoginResponseMessage(av);
                    resp.setOrgId(orgId);
                    userInfo.setSessionId(currentUser.getSession().getId());
                    if (resp.isSuccess())
                        LogManager.startUser(cc.getEngineConfiguration().getLogPath(), cc.getEnv().getId(), userInfo);
                } else {
                    // 由用户选择机构
                    resp = new LoginResponseMessage(orgs);
                }
                resp.setType(userInfo.getType());
                resp.setName(userInfo.getName());
                cc.setUser(userInfo);
                if (remainingDays > 0)
                    resp.setTooltip(I18N.getLabel("app.user.expiring", remainingDays));
                else if (cc.getProjectService().getConfig().getCheckPasswordPattern() != null) {
                    Matcher matcher = cc.getProjectService().getConfig().getCheckPasswordPattern().matcher(loginVo.getPassword());
                    if (!matcher.find())
                        resp.setTooltip(cc.getProjectService().getConfig().getCheckPasswordMessage());
                }
                IdentityUtils.log(cc, dao);
                return resp;
            } catch (Exception ex) {
                if (log.isErrorEnabled())
                    log.error("login", ex);
                LogManager.stopUser(userInfo);
                ((ISubject) currentUser).setAuthenticate(false);
                return MessageUtils
                        .userAuthorizationError(userInfo.getId(), ex);
            }
        } else {
            IProjectService ps = cc.getProjectService();
            DeviceMapBean dm = ps.getDeviceMap(cc.getEnv().getPortlet(), cc
                    .getEnv().getDeviceType().getName());
            return new LoginResponseMessage(EntityUtils.parsePage(cc, dm.getMainPage()));
        }
    }

    final static IResponseMessage<?> doCallback(CommandContext cc, String cb) {
        LogicBean lb = cc.getProjectService().getEntityHandler().getEntity(cb);
        if (lb == null)
            return MessageUtils.entityNotFound(EntityType.LOGIC.getName(), cb);
        Map<String, Object> systemVariables = new HashMap<String, Object>();
        RuntimeUtils.initWorkflow(systemVariables, null);
        RecordContext rc = new RecordContext(new Record(), systemVariables, new HashMap<>());
        rc.setVariable(new FieldDo("type", FieldType.INT, 1));//设置类型
        Object result = RuntimeUtils.evaluate(cc, lb, rc);//返回手机号码
        if (result instanceof String) {
            if ("i000".equals(result))//返回手机号
                return new SimpleResponseMessage(rc.getParameter("755"));
            if ("0000".equals(result))
                return new SimpleResponseMessage();
            return new SimpleResponseMessage((String) result, cc.getMessage(
                    (String) result, rc));
        } else
            return new SimpleResponseMessage();
    }

    @Override
    public String getName() {
        return "login.Login";
    }
}
